package com.shujia

import org.junit.{Before, Test}

import scala.collection.immutable
import scala.io.{BufferedSource, Source}

class Demo23Student {
  var students: List[Students] = _
  var scores: List[Score] = _
  var subject: List[Subject] = _

  @Before
  def read_file(): Unit = {
    // 读取students数据并构建List，以Students样例类对象作为List中的每个元素
    val stu_bs: BufferedSource = Source.fromFile("data/students.txt")
    students = stu_bs
      .getLines()
      .toList
      .map(line => {
        val splits: Array[String] = line.split(",")
        Students(splits(0).toInt, splits(1), splits(2).toInt, splits(3), splits(4))
      })
    stu_bs.close()

    // 读取score数据构建List，以Score样例类对象作为List中的每个元素
    val sco_bs: BufferedSource = Source.fromFile("data/score.txt")

    scores = sco_bs
      .getLines()
      .toList
      .map(line => {
        val splits: Array[String] = line.split(",")
        Score(splits(0).toInt, splits(1).toInt, splits(2).toInt)
      })
    sco_bs.close()

    // 读取subject数据构建List，以Subject样例类对象作为List中的每个元素
    val sub_bs: BufferedSource = Source.fromFile("data/subject.txt")

    subject = sub_bs
      .getLines()
      .toList
      .map(line => {
        val splits: Array[String] = line.split(",")
        Subject(splits(0).toInt, splits(1), splits(2).toInt)
      })
    sub_bs.close()

  }

  @Test
  // 将students、scores、subject数据打印
  def printThreeData(): Unit = {
    students.take(10).foreach(println)
    scores.take(10).foreach(println)
    subject.take(10).foreach(println)
  }

  /**
   * 1、统计班级人数
   * 2、统计学生的总分
   */
  @Test
  // 统计班级人数
  def clazz_num(): Unit = {
    students
      .groupBy(stu => stu.clazz)
      .map(kv => kv._1 + "," + kv._2.length)
      .foreach(println)
  }

  @Test
  // 统计学生的总分
  def sum_score(): Unit = {
    val sum_sco: immutable.Iterable[String] = scores
      .groupBy(sco => sco.id)
      .map {
        case (id: Int, scores: List[Score]) =>
          val score: List[Int] = scores.map(sco => sco.score)
          id + "," + score.sum
      }

    val stu_id_map: Map[Int, Students] = students
      .map(stu => (stu.id, stu))
      .toMap

    sum_sco
      .map(id_sum_score => {
        val splits: Array[String] = id_sum_score.split(",")
        val id: Int = splits(0).toInt
        val stu_sum_score: String = splits(1)
        val stu: Students = stu_id_map(id)
        val name: String = stu.name
        val clazz: String = stu.clazz
        s"$id,$name,$clazz,$stu_sum_score"
      }).foreach(println)


  }

  @Test
  // 1、统计年级排名前十学生各科的分数 [学号,学生姓名，学生班级，科目名，分数]
  def question1(): Unit = {
    val top10ids: List[Int] = scores
      .groupBy(_.id) // 按学生id分组 求学生总分
      .map {
        case (id: Int, scoreList: List[Score]) =>
          val sum_score: Int = scoreList.map(_.score).sum
          (id, sum_score)
      }
      .toList
      .sortBy(-_._2)
      .map(_._1)
      .take(10)


    val id_stu_map: Map[Int, Students] = students
      // 将top10的学生过滤出来
      .filter(stu => {
        top10ids.contains(stu.id)
      })
      .map(stu => (stu.id, stu))
      .toMap

    val sid_name_map: Map[Int, String] = subject
      .map(sub => (sub.subject_id, sub.subject_name))
      .toMap

    scores
      .filter(sco => {
        top10ids.contains(sco.id)
      })
      .map(sco => {
        val stu: Students = id_stu_map(sco.id)
        val sub_name: String = sid_name_map(sco.subject_id)
        s"${sco.id},${stu.name},${stu.clazz},${sub_name},${sco.score}"
      }).foreach(println)

  }

  @Test
  // 2、统计总分大于年级平均分的学生 [学号，姓名，班级，总分]
  def question2(): Unit = {
    val sum_scores: List[(Int, Int)] = scores
      .groupBy(_.id) // 按学生id分组 求学生总分
      .map {
        case (id: Int, scoreList: List[Score]) =>
          val sum_score: Int = scoreList.map(_.score).sum
          (id, sum_score)
      }.toList

    // 计算年级平均分
    val avg_score: Double = sum_scores.map(_._2).sum / sum_scores.length.toDouble
    println(s"年级平均分是$avg_score")

    val id_stu_map: Map[Int, Students] =
      students
        .map(stu => (stu.id, stu))
        .toMap


    sum_scores
      .filter { // 过滤出总比大于年级平均分的学生
        case (id: Int, sum_score: Int) =>
          sum_score > avg_score
      }
      .map {
        case (id: Int, sum_score: Int) =>
          val stu: Students = id_stu_map(id)
          s"$id,${stu.name},${stu.clazz},$sum_score"
      }.foreach(println)
  }

  @Test
  // 3、统计每科都及格的学生 [学号，姓名，班级，科目，分数]
  def question3(): Unit = {

    val sid_sub_map: Map[Int, Subject] = subject
      .map(sub => (sub.subject_id, sub))
      .toMap

    val avg_sub_pass_ids: List[Int] = scores
      .map(sco => {
        val sub: Subject = sid_sub_map(sco.subject_id)
        (sco.id, sco.score, sub.subject_score * 0.6)
      }).filter {
      case (id: Int, sco: Int, sub_pass_score: Double) =>
        sco >= sub_pass_score
    }.groupBy(_._1)
      .map {
        // 统计每个学生有多少门科目及格了
        case (id: Int, scoList: List[(Int, Int, Double)]) =>
          (id, scoList.length)
      }.filter(_._2 == 6)
      .map(_._1)
      .toList

    val id_stu_map: Map[Int, Students] = students
      // 将top10的学生过滤出来
      .filter(stu => {
        avg_sub_pass_ids.contains(stu.id)
      })
      .map(stu => (stu.id, stu))
      .toMap

    val sid_name_map: Map[Int, String] = subject
      .map(sub => (sub.subject_id, sub.subject_name))
      .toMap

    scores
      .filter(sco => {
        avg_sub_pass_ids.contains(sco.id)
      })
      .map(sco => {
        val stu: Students = id_stu_map(sco.id)
        val sub_name: String = sid_name_map(sco.subject_id)
        s"${sco.id},${stu.name},${stu.clazz},${sub_name},${sco.score}"
      }).foreach(println)

  }

  @Test
  // 4、统计每个班级的前三名 [学号，姓名，班级，分数]
  def question4(): Unit = {
    val id_sum_score_map: Map[Int, Int] = scores
      .groupBy(_.id) // 按学生id分组 求学生总分
      .map {
        case (id: Int, scoreList: List[Score]) =>
          val sum_score: Int = scoreList.map(_.score).sum
          (id, sum_score)
      } // groupBy之后默认会变成Map 如果后面的map方法中传入的函数返回的也是二元组，则最后map的返回也会变成Map

    students
      .map(stu => {
        val sum_score: Int = id_sum_score_map(stu.id)
        (stu.id, stu.name, stu.clazz, sum_score)
      }).groupBy(_._3)
      .flatMap {
        case (clazz: String, stuSumScoreList: List[(Int, String, String, Int)]) =>
          stuSumScoreList.sortBy(-_._4).take(3)
      }.foreach(println)

  }

  @Test
  // 5、统计偏科最严重的前100名学生  [学号，姓名，班级，科目，分数]
  // 用方差去描述偏科程度
  def question5(): Unit = {
    // 归一化
    // 将所有的分数转换成百分制

    val sid_score_map: Map[Int, Int] = subject.map(sub => (sub.subject_id, sub.subject_score)).toMap

    val new_scores: List[(Int, Int, Double)] = scores
      .map(sco => {
        val sub_score: Int = sid_score_map(sco.subject_id)
        (sco.id, sco.subject_id, sco.score / sub_score.toDouble * 100)
      })

    // 计算方差
    // 偏科最严重的前100名学生id
    val ids: List[Int] = new_scores
      .groupBy(_._1)
      .map {
        case (id: Int, scoreList: List[(Int, Int, Double)]) =>
          // 计算平均分
          val avg_score: Double = scoreList.map(_._3).sum / scoreList.length
          // 计算方差
          // 计算 Sum(x-avg_score)^2 / N
          val variance: Double = scoreList.map {
            case (id: Int, sid: Int, new_score: Double) =>
              Math.pow(new_score - avg_score, 2)
          }.sum / scoreList.length
          (id, variance)
      }.toList
      .sortBy(-_._2)
      .take(100)
      .map(_._1)

    val id_stu_map: Map[Int, Students] = students
      // 将top10的学生过滤出来
      .filter(stu => {
        ids.contains(stu.id)
      })
      .map(stu => (stu.id, stu))
      .toMap

    val sid_name_map: Map[Int, String] = subject
      .map(sub => (sub.subject_id, sub.subject_name))
      .toMap

    scores
      .filter(sco => {
        ids.contains(sco.id)
      })
      .map(sco => {
        val stu: Students = id_stu_map(sco.id)
        val sub_name: String = sid_name_map(sco.subject_id)
        s"${sco.id},${stu.name},${stu.clazz},${sub_name},${sco.score}"
      }).foreach(println)

  }


}

case class Students(id: Int, name: String, age: Int, gender: String, clazz: String)

case class Score(id: Int, subject_id: Int, score: Int)

case class Subject(subject_id: Int, subject_name: String, subject_score: Int)
